﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.IO;

namespace Compactador
{
    public partial class Form1 : Form
    {
        private string arquivo;
        private int tamanhoOriginal = 0;
        private int tamanhoCodificado = 0;
        private double tamanhoMedio = 0;
        private string textoFiltrado;
        private double entropia = 0;
        private List<CaracterOcorrencia> ocorrencias = new List<CaracterOcorrencia>();
        private Dictionary<char, string> codigoHuffman;
        private Dictionary<char, int> qtdOcorrencia = new Dictionary<char, int>();

        public Form1()
        {
            InitializeComponent();
            //ConverteCaracteres("a");
            openFileDialog1.InitialDirectory = @".";
            openFileDialog1.Filter = "Text|*.txt";            
        }

        private void ConverteCaracteres()
        {
            string texto = File.ReadAllText(arquivo, Encoding.Default);
            string textoConvertido;
            StringBuilder sb = new StringBuilder();
            StringBuilder sbApenasLetras = new StringBuilder();
            // converter para caracteres minúsculos
            texto = texto.ToLower();
            // converter caracteres de quebra de linha, tabulação e retorno de carro em espaço
            // está sendo feito aqui por que o StringBuffer não reconhece tais caracteres
            texto = texto.Replace('\n', ' ');
            texto = texto.Replace('\r', ' ');
            texto = texto.Replace('\t', ' ');
            sb.Append(texto);
            // converter hífen em espaço
            sb.Replace('-', ' ');
            // substituir acentuações
            sb.Replace('á', 'a');
            sb.Replace('ã', 'a');
            sb.Replace('à', 'a');
            sb.Replace('â', 'a');
            sb.Replace('ä', 'a');
            sb.Replace('é', 'e');
            sb.Replace('ê', 'e');
            sb.Replace('ë', 'e');
            sb.Replace('è', 'e');
            sb.Replace('í', 'i');
            sb.Replace('ì', 'i');
            sb.Replace('ï', 'i');
            sb.Replace('î', 'i');
            sb.Replace('ó', 'o');
            sb.Replace('ò', 'o');
            sb.Replace('ö', 'o');
            sb.Replace('ô', 'o');
            sb.Replace('ú', 'u');
            sb.Replace('ù', 'u');
            sb.Replace('ü', 'u');
            sb.Replace('û', 'u');                      

            // permanecer apenas com letras e espaços
            textoConvertido = sb.ToString();
            for (int i = 0; i < textoConvertido.Length; i++)
            {
                if ((textoConvertido[i] >= 'a' && textoConvertido[i] <= 'z') || textoConvertido[i] == ' ')
                {
                    sbApenasLetras.Append(textoConvertido[i]);
                }
            }

            textoFiltrado = sbApenasLetras.ToString();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
        }

        private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
        {
            txtArquivo.Text = openFileDialog1.FileName;
            arquivo = txtArquivo.Text;
        }

        private void CalcularEntropia()
        {
            // tamanho em binits do texto original
            tamanhoOriginal = textoFiltrado.Length * 7;
            //contar a ocorrência de cada caracter            
            for (int i = 0; i < textoFiltrado.Length; i++)
            {
                if (qtdOcorrencia.ContainsKey(textoFiltrado[i]))
                {
                    qtdOcorrencia[textoFiltrado[i]] = qtdOcorrencia[textoFiltrado[i]] + 1;
                }
                else
                {
                    qtdOcorrencia.Add(textoFiltrado[i], 1);
                }                
            }
            
            foreach (KeyValuePair<char, int> valor in qtdOcorrencia)
            {                
                ocorrencias.Add(new CaracterOcorrencia(valor.Key.ToString(), valor.Value, Convert.ToDouble(valor.Value) / Convert.ToDouble(textoFiltrado.Length)));               
            }
            
            ocorrencias.Sort(new Comparison<CaracterOcorrencia>(ComparaCaracterOcorrenciaPorChar));                        
            entropia = Calculos.CalcularEntropia(ocorrencias);
            lblEntropia.Text = entropia.ToString();            
        }

        private static int ComparaCaracterOcorrenciaPorChar(CaracterOcorrencia co1, CaracterOcorrencia co2)
        {
            if (co1.Caracter[0] < co2.Caracter[0])
            {
                return -1;
            }
            if (co1.Caracter[0] > co2.Caracter[0])
            {
                return 1;
            }
            return 0;
        }

        private void btProcessar_Click(object sender, EventArgs e)
        {
            // 10000 ticks equivalem a 1 milisegundo
            long i = DateTime.Now.Ticks/10000;
            ConverteCaracteres();
            CalcularEntropia();
            lblTamOriginal.Text = string.Format("{0} binits", tamanhoOriginal.ToString());
            CalcularHuffman();
            ComprimentoMedio();
            foreach (CaracterOcorrencia co in ocorrencias)
            {
                co.CodigoHuffman = codigoHuffman[co.Caracter[0]];
                tamanhoCodificado += co.CodigoHuffman.Length * qtdOcorrencia[co.Caracter[0]];
            }
            lblTamCodificado.Text = string.Format("{0} binits", tamanhoCodificado.ToString());
            lblFatorCompactacao.Text = string.Format("{0:0.00%}", 1 - Convert.ToDouble(tamanhoCodificado)/Convert.ToDouble(tamanhoOriginal));
            lblTamanhoMedio.Text = string.Format("{0:#.####} binits/símbolo", tamanhoMedio);
            lblHL.Text = string.Format("{0:0.0000}", entropia/tamanhoMedio);
            lblRedundancia.Text = string.Format("{0:0.0000}", 1 - entropia / tamanhoMedio);
            dgOcorrencias.DataSource = ocorrencias;
            long f = DateTime.Now.Ticks/10000;
            lblTempoTotal.Text = string.Format("{0} milisegundos", f - i);
        }

        private void CalcularHuffman()
        {
            codigoHuffman = Calculos.CalcularHuffman(ocorrencias);
        }

        public void ComprimentoMedio()
        {
            tamanhoMedio = Calculos.TamanhoMedio();
        }
    }
}